package javasec.contempt.data;

/* Seed Connector
 * 
 * The seed connector controls all the connection management in contempt
 * 
 * When contempt starts, it gets a list of known seeds from a local database.
 * This 
 * 
 * SeedConnection[] getConnections() // pull a list of connections from the database
 * String[] getConnected()   // get a list of names of connected servers
 *  
 * newConnection(name, location)
 * 
 * 
 */


import java.io.File;
import java.io.FileInputStream;
import java.net.Socket;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;

import javasec.contempt.consoles.SeedShellConsole;
import javasec.contempt.data.SeedConnector;
import javasec.contempt.data.SeedNotifier;
import javasec.contempt.dialogs.PasswordDialog;
import javasec.contempt.dialogs.ProvisionDialog;
import javasec.seed.DataSeedInterface;
import javasec.seed.ManagerSeedInterface;
import javasec.seed.SeedInterface;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.rmi.ssl.SslRMIClientSocketFactory;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tray;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.plugin.AbstractUIPlugin;


import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.KeyStore;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import sun.security.x509.X500Name;
import sun.security.x509.CertAndKeyGen;
import java.util.Date;
import java.io.FileOutputStream;


public class SeedConnector
{
	public static SeedConnector instance=null;
	public static IWorkbenchWindow window=null;
	public static Display display=Display.getCurrent();
	
	private static String fingerprintString="";
	private static X509Certificate cert=null;

	public static String globalPass=null;
	public static Connection contemptDB=null; // database that stored contempt settings	
	public static String contemptPath=System.getProperty("user.home")+File.separator+".contempt";
	public static ArrayList<SeedConnection> connections=new ArrayList<SeedConnection>();
	
	public static String idPath=contemptPath+File.separator+"contempt-id.key";
	public static String friendsPath=contemptPath+File.separator+"contempt-friends.key";
	public static String serversPath=contemptPath+File.separator+"contempt-trusted.key";
	
	public SeedConnector()
	{
		if(instance==null)instance=this;
		else
		{
			System.out.println("warning: SeedConnector should only be instantiated once");
		}
		
		try
		{
			// if this is the first time running contempt, run the provision wizard
			if(!new File(contemptPath).exists() ||
				!new File(contemptPath+File.separator+"contempt.db").exists())
			{
				ProvisionDialog dialog=new ProvisionDialog(null);
				dialog.open();
				if(dialog.aborted)System.exit(-1);
			}
			// else connect like normal
			else
			{
				// prompt the user for the global password
				PasswordDialog passwordDialog=new PasswordDialog(null);
				passwordDialog.open();
				if(passwordDialog.aborted)System.exit(0);
				globalPass=passwordDialog.getString();
				
				// start up the database driver
				Class.forName("org.sqlite.JDBC");
				SeedConnector.contemptDB=DriverManager.getConnection("jdbc:sqlite:"+SeedConnector.contemptPath+File.separator+"contempt.db");
			}
			
			// query the database for connections, and populate the connection list
			Statement stat = contemptDB.createStatement();
			ResultSet rs = stat.executeQuery("select * from connections;");
			while(rs.next())
			{
				System.out.println("adding "+rs.getString("name")+" to the list");
				connections.add(new SeedConnection(rs.getString("name"),rs.getString("location")));
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		// set the key stores
		System.setProperty("javax.net.ssl.keyStore",contemptPath+File.separator+"contempt-id.key");
		System.setProperty("javax.net.ssl.keyStorePassword",SeedConnector.globalPass);
		System.setProperty("javax.net.ssl.trustStore",contemptPath+File.separator+"contempt-trusted.key");
		System.setProperty("javax.net.ssl.trustStorePassword",SeedConnector.globalPass);
	}
	
	public static SeedConnection[] getConnections()
	{
		System.out.println("found "+connections.size()+" connections");
		return connections.toArray(new SeedConnection[]{});
	}
	
	
	public static SeedConnection getConnection(String name)
	{
		return null;
	}
	
	public static SeedConnection newConnection(String name, String location)
	{
		System.out.println("new connection "+name+" "+location);
		
		String trustStorePath=System.getProperty("javax.net.ssl.trustStore");
		KeyStore trustStore=null;
		
		try
		{	
			// now we make an insecure connection to the server
			// to figure out what the fingerprint is
			// code from http://www.exampledepot.com/egs/javax.net.ssl/TrustAll.html

			trustStore=KeyStore.getInstance(KeyStore.getDefaultType());
			trustStore.load(new FileInputStream(trustStorePath),SeedConnector.globalPass.toCharArray());
			
			TrustManager[] trustAllCerts = new TrustManager[]
			{
				new X509TrustManager()
				{
					public java.security.cert.X509Certificate[] getAcceptedIssuers()
					{
						return null;
					}
					public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
					{
					}
					public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
					{
					}
				}
			};
			        			    
			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			final SSLSocketFactory ssf=sc.getSocketFactory();
			SSLSocket insecureSocket=(SSLSocket)ssf.createSocket(location,41414);

			HandshakeCompletedListener hsl=new HandshakeCompletedListener()
			{
				public void handshakeCompleted(HandshakeCompletedEvent handshake)
				{
					try
					{
						cert=(X509Certificate)handshake.getPeerCertificates()[0];
						
						byte[] fingerprintBytes=MessageDigest.getInstance("SHA1").digest(cert.getEncoded());
						
						for(int x=0;x<fingerprintBytes.length;++x)
						{
							byte b=fingerprintBytes[x];
							if(Integer.toString(b&0xFF,16).length()==1)fingerprintString+="0";
							fingerprintString+=Integer.toString(b&0xFF,16);
							if((x+1)<fingerprintBytes.length)fingerprintString+=":";
						}	
					}
					catch(Exception e)
					{
						System.out.println("unable to verify remote host\n"+e);
						e.printStackTrace();
					}
				}
			};

			insecureSocket.addHandshakeCompletedListener(hsl);
			insecureSocket.startHandshake();

			// wait for the fingerprint to come back
			while(fingerprintString=="")
			{
				System.out.println("waiting for handshake");
				Thread.sleep(1000);
			}

			
			// get the name of the remote server			
			SslRMIClientSocketFactory insecureCSF=new SslRMIClientSocketFactory()
			{
				public Socket createSocket(String host,int port)
				{
					try
					{
						// return a socket from our insecure socket factory
						return ssf.createSocket(host,port);
					}
					catch(Exception e)
					{
						e.printStackTrace();
						return null;
					}
				}
			};
			
			//Registry rmiRegistry=LocateRegistry.getRegistry(hostname,41414,insecureCSF);
			//ManagerSeedInterface managerSeed=(ManagerSeedInterface)rmiRegistry.lookup("ManagerSeed");
			//String serverName=hostname; //managerSeed.getName();

			//String serverName="remoteTest";
			
			// pop up the accept/deny dialog
			boolean acceptKey=MessageDialog.openQuestion(
				SeedConnector.window.getShell(),
				"verify fingerprint",
				"The SHA1 fingerprint of "+name+" is\n"+
				fingerprintString+"\naccept this fingerprint as trusted?");
				
			// add the cert to the truststore
			if(acceptKey==true)
			{
				trustStore=KeyStore.getInstance(KeyStore.getDefaultType());
				trustStore.load(new FileInputStream(trustStorePath),SeedConnector.globalPass.toCharArray());
				trustStore.setCertificateEntry(name,cert);
				trustStore.store(new FileOutputStream(trustStorePath),SeedConnector.globalPass.toCharArray());
			}
		
			Connection conn=SeedConnector.contemptDB;
			PreparedStatement ps = conn.prepareStatement("insert into connections values (?, ?);");

			ps.setString(1,name);
			ps.setString(2,location);
			ps.execute();
			
			SeedConnection connection=new SeedConnection(name,location);
			//connections.put(serverName,connection);
			return connection;
		}
		catch(Exception e)
		{
			MessageDialog.openError(
				window.getShell(),
				"connection error",
				"error: "+e);			
				
			e.printStackTrace();
			return null;
		}
	}

	public static void removeConnection(SeedConnection seedConnection)
	{
		Connection conn=SeedConnector.contemptDB;
		
		try
		{
			PreparedStatement ps = conn.prepareStatement("delete from connections where name=?;");
			
			ps.setString(1,seedConnection.name);
			ps.execute();
			
			SeedConnector.connections.remove(seedConnection);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void renameConnection(SeedConnection seedConnection, String newName)
	{
		Connection conn=SeedConnector.contemptDB;
		
		try
		{
			PreparedStatement ps = conn.prepareStatement("update connections SET name=? WHERE location=?;");
			
			ps.setString(1,newName);
			ps.setString(2,seedConnection.location);
			ps.execute();
		
			seedConnection.name=newName;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}
